From 38b5c8cf45e53ced9fd617332cdaf3a87601d451 Mon Sep 17 00:00:00 2001 From: Tristan Van Berkom Date: Sun, 6 Mar 2011 15:13:56 +0900 Subject: [PATCH] Cache heights-for-range-of-widths instead of height for every width. This patch optimizes window resizes by assuming that if a widget has the same height at a width of 50 as with a width of 150, the height for width 100 will also be the same. The patch also further optimizes the cache allocator, now there are 2 pointer arrays of up to a maximum of 5 requests, the arrays will only be allocated if a request is ever made in that orientation and the array will be sparse until each request is made (i.e. if a label can only wrap to 3 lines, there will only be 3 out of a possible 5 SizeRequest structures allocated to cache it). --- gtk/gtksizerequest.c | 411 ++++++++++++++++++++++++----------------- gtk/gtkwidget.c | 3 +- gtk/gtkwidgetprivate.h | 42 ++--- 3 files changed, 264 insertions(+), 192 deletions(-) diff --git a/gtk/gtksizerequest.c b/gtk/gtksizerequest.c index b27fd05f17..95cf2fafd4 100644 --- a/gtk/gtksizerequest.c +++ b/gtk/gtksizerequest.c @@ -31,126 +31,235 @@ #include "gtksizegroup-private.h" #include "gtkwidgetprivate.h" -/* looks for a cached size request for this for_size. If not - * found, returns the oldest entry so it can be overwritten - * - * Note that this caching code was originally derived from - * the Clutter toolkit. - */ +#ifndef G_DISABLE_CHECKS +static GQuark recursion_check_quark = 0; +#endif /* G_DISABLE_CHECKS */ + +static void +push_recursion_check (GtkWidget *widget, + GtkSizeGroupMode orientation, + gint for_size) +{ +#ifndef G_DISABLE_CHECKS + const char *previous_method; + const char *method; + + if (recursion_check_quark == 0) + recursion_check_quark = g_quark_from_static_string ("gtk-size-request-in-progress"); + + previous_method = g_object_get_qdata (G_OBJECT (widget), recursion_check_quark); + + if (orientation == GTK_SIZE_GROUP_HORIZONTAL) + { + method = for_size < 0 ? "get_width" : "get_width_for_height"; + } + else + { + method = for_size < 0 ? "get_height" : "get_height_for_width"; + } + + if (previous_method != NULL) + { + g_warning ("%s %p: widget tried to gtk_widget_%s inside " + " GtkWidget ::%s implementation. " + "Should just invoke GTK_WIDGET_GET_CLASS(widget)->%s " + "directly rather than using gtk_widget_%s", + G_OBJECT_TYPE_NAME (widget), widget, + method, previous_method, + method, method); + } + + g_object_set_qdata (G_OBJECT (widget), recursion_check_quark, (char*) method); +#endif /* G_DISABLE_CHECKS */ +} + +static void +pop_recursion_check (GtkWidget *widget, + GtkSizeGroupMode orientation) +{ +#ifndef G_DISABLE_CHECKS + g_object_set_qdata (G_OBJECT (widget), recursion_check_quark, NULL); +#endif +} + + +static void +clear_cache (SizeRequestCache *cache, + GtkSizeGroupMode orientation) +{ + SizeRequest **sizes; + gint i; + + if (orientation == GTK_SIZE_GROUP_HORIZONTAL) + { + sizes = cache->widths; + + cache->widths = NULL; + cache->cached_widths = 0; + cache->last_cached_width = 0; + cache->cached_base_width = FALSE; + } + else + { + sizes = cache->heights; + + cache->heights = NULL; + cache->cached_heights = 0; + cache->last_cached_height = 0; + cache->cached_base_height = FALSE; + } + + if (sizes) + { + for (i = 0; sizes[i] != NULL; i++) + g_slice_free (SizeRequest, sizes[i]); + + g_slice_free1 (sizeof (SizeRequest *) * GTK_SIZE_REQUEST_CACHED_SIZES, sizes); + } +} + +void +_gtk_widget_free_cached_sizes (GtkWidget *widget) +{ + SizeRequestCache *cache; + + cache = _gtk_widget_peek_request_cache (widget); + + clear_cache (cache, GTK_SIZE_GROUP_HORIZONTAL); + clear_cache (cache, GTK_SIZE_GROUP_VERTICAL); +} /* This function checks if 'request_needed' flag is present * and resets the cache state if a request is needed for * a given orientation. */ static SizeRequestCache * -init_cache (GtkWidget *widget, - GtkSizeGroupMode orientation) +init_cache (GtkWidget *widget) { - GtkSizeRequestMode mode; - SizeRequestCache *cache; + SizeRequestCache *cache; cache = _gtk_widget_peek_request_cache (widget); - if (orientation == GTK_SIZE_GROUP_HORIZONTAL && - _gtk_widget_get_width_request_needed (widget)) - { - mode = gtk_widget_get_request_mode (widget); + if (_gtk_widget_get_width_request_needed (widget)) + clear_cache (cache, GTK_SIZE_GROUP_HORIZONTAL); + else if (_gtk_widget_get_height_request_needed (widget)) + clear_cache (cache, GTK_SIZE_GROUP_VERTICAL); + + return cache; +} + +/* looks for a cached size request for this for_size. If not + * found, returns the oldest entry so it can be overwritten + * + * Note that this caching code was originally derived from + * the Clutter toolkit but has evolved for other GTK+ requirements. + */ +static gboolean +get_cached_size (GtkWidget *widget, + GtkSizeGroupMode orientation, + gint for_size, + CachedSize **result) +{ + SizeRequestCache *cache; + SizeRequest **cached_sizes; + guint i, n_sizes; + + cache = init_cache (widget); - if (mode == GTK_SIZE_REQUEST_CONSTANT_SIZE) + if (for_size < 0) + { + if (orientation == GTK_SIZE_GROUP_HORIZONTAL) { - if (cache->sizes) - { - g_slice_free (ContextualSizes, cache->sizes); - cache->sizes = NULL; - } + *result = &cache->cached_width; + return cache->cached_base_width; } else { - if (!cache->sizes) - cache->sizes = g_slice_new0 (ContextualSizes); - - memset (cache->sizes->widths, 0x0, GTK_SIZE_REQUEST_CACHED_SIZES * sizeof (SizeRequest)); - cache->sizes->cached_widths = 0; - cache->sizes->last_cached_width = 0; + *result = &cache->cached_height; + return cache->cached_base_height; } + } - cache->cached_width.minimum_size = -1; - cache->cached_width.natural_size = -1; + if (orientation == GTK_SIZE_GROUP_HORIZONTAL) + { + cached_sizes = cache->widths; + n_sizes = cache->cached_widths; } - else if (orientation == GTK_SIZE_GROUP_VERTICAL && - _gtk_widget_get_height_request_needed (widget)) + else { - mode = gtk_widget_get_request_mode (widget); + cached_sizes = cache->heights; + n_sizes = cache->cached_heights; + } - if (mode == GTK_SIZE_REQUEST_CONSTANT_SIZE) - { - if (cache->sizes) - { - g_slice_free (ContextualSizes, cache->sizes); - cache->sizes = NULL; - } - } - else + /* Search for an already cached size */ + for (i = 0; i < n_sizes; i++) + { + if (cached_sizes[i]->lower_for_size <= for_size && + cached_sizes[i]->upper_for_size >= for_size) { - if (!cache->sizes) - cache->sizes = g_slice_new0 (ContextualSizes); - - memset (cache->sizes->heights, 0x0, GTK_SIZE_REQUEST_CACHED_SIZES * sizeof (SizeRequest)); - cache->sizes->cached_heights = 0; - cache->sizes->last_cached_height = 0; + *result = &cached_sizes[i]->cached_size; + return TRUE; } - - cache->cached_height.minimum_size = -1; - cache->cached_height.natural_size = -1; } - return cache; + return FALSE; } -static gboolean -get_cached_size (GtkWidget *widget, - GtkSizeGroupMode orientation, - gint for_size, - CachedSize **result) +static void +commit_cached_size (GtkWidget *widget, + GtkSizeGroupMode orientation, + gint for_size, + gint minimum_size, + gint natural_size) { - SizeRequestCache *cache; - SizeRequest *cached_sizes; - guint i, n_sizes; + SizeRequestCache *cache; + SizeRequest **cached_sizes; + guint i, n_sizes; - cache = init_cache (widget, orientation); + cache = _gtk_widget_peek_request_cache (widget); + /* First handle caching of the base requests */ if (for_size < 0) { if (orientation == GTK_SIZE_GROUP_HORIZONTAL) - *result = &cache->cached_width; - else - *result = &cache->cached_height; - - if ((*result)->minimum_size < 0) - return FALSE; + { + cache->cached_width.minimum_size = minimum_size; + cache->cached_width.natural_size = natural_size; + cache->cached_base_width = TRUE; + } else - return TRUE; + { + cache->cached_height.minimum_size = minimum_size; + cache->cached_height.natural_size = natural_size; + cache->cached_base_height = TRUE; + } + return; } + /* Check if the minimum_size and natural_size is already + * in the cache and if this result can be used to extend + * that cache entry + */ if (orientation == GTK_SIZE_GROUP_HORIZONTAL) { - cached_sizes = cache->sizes->widths; - n_sizes = cache->sizes->cached_widths; + cached_sizes = cache->widths; + n_sizes = cache->cached_widths; } else { - cached_sizes = cache->sizes->heights; - n_sizes = cache->sizes->cached_widths; + cached_sizes = cache->heights; + n_sizes = cache->cached_heights; } - /* Search for an already cached size */ for (i = 0; i < n_sizes; i++) { - if (cached_sizes[i].for_size == for_size) + if (cached_sizes[i]->cached_size.minimum_size == minimum_size && + cached_sizes[i]->cached_size.natural_size == natural_size) { - *result = &cached_sizes[i].cached_size; - return TRUE; + cached_sizes[i]->lower_for_size = MIN (cached_sizes[i]->lower_for_size, for_size); + cached_sizes[i]->upper_for_size = MAX (cached_sizes[i]->upper_for_size, for_size); + return; } } @@ -159,90 +268,52 @@ get_cached_size (GtkWidget *widget, * and increment the last_cached_width/height right away */ if (orientation == GTK_SIZE_GROUP_HORIZONTAL) { - if (cache->sizes->cached_widths < GTK_SIZE_REQUEST_CACHED_SIZES) + if (cache->cached_widths < GTK_SIZE_REQUEST_CACHED_SIZES) { - cache->sizes->cached_widths++; - cache->sizes->last_cached_width = cache->sizes->cached_widths - 1; + cache->cached_widths++; + cache->last_cached_width = cache->cached_widths - 1; } else { - if (++cache->sizes->last_cached_width == GTK_SIZE_REQUEST_CACHED_SIZES) - cache->sizes->last_cached_width = 0; + if (++cache->last_cached_width == GTK_SIZE_REQUEST_CACHED_SIZES) + cache->last_cached_width = 0; } - cache->sizes->widths[cache->sizes->last_cached_width].for_size = for_size; - *result = &cache->sizes->widths[cache->sizes->last_cached_width].cached_size; + if (!cache->widths) + cache->widths = g_slice_alloc0 (sizeof (SizeRequest *) * GTK_SIZE_REQUEST_CACHED_SIZES); + + if (!cache->widths[cache->last_cached_width]) + cache->widths[cache->last_cached_width] = g_slice_new (SizeRequest); + + cache->widths[cache->last_cached_width]->lower_for_size = for_size; + cache->widths[cache->last_cached_width]->upper_for_size = for_size; + cache->widths[cache->last_cached_width]->cached_size.minimum_size = minimum_size; + cache->widths[cache->last_cached_width]->cached_size.natural_size = natural_size; } else /* GTK_SIZE_GROUP_VERTICAL */ { - if (cache->sizes->cached_heights < GTK_SIZE_REQUEST_CACHED_SIZES) + if (cache->cached_heights < GTK_SIZE_REQUEST_CACHED_SIZES) { - cache->sizes->cached_heights++; - cache->sizes->last_cached_height = cache->sizes->cached_heights - 1; + cache->cached_heights++; + cache->last_cached_height = cache->cached_heights - 1; } else { - if (++cache->sizes->last_cached_height == GTK_SIZE_REQUEST_CACHED_SIZES) - cache->sizes->last_cached_height = 0; + if (++cache->last_cached_height == GTK_SIZE_REQUEST_CACHED_SIZES) + cache->last_cached_height = 0; } - cache->sizes->heights[cache->sizes->last_cached_height].for_size = for_size; - *result = &cache->sizes->heights[cache->sizes->last_cached_height].cached_size; - } - - return FALSE; -} - + if (!cache->heights) + cache->heights = g_slice_alloc0 (sizeof (SizeRequest *) * GTK_SIZE_REQUEST_CACHED_SIZES); -#ifndef G_DISABLE_CHECKS -static GQuark recursion_check_quark = 0; -#endif /* G_DISABLE_CHECKS */ + if (!cache->heights[cache->last_cached_height]) + cache->heights[cache->last_cached_height] = g_slice_new (SizeRequest); -static void -push_recursion_check (GtkWidget *widget, - GtkSizeGroupMode orientation, - gint for_size) -{ -#ifndef G_DISABLE_CHECKS - const char *previous_method; - const char *method; - - if (recursion_check_quark == 0) - recursion_check_quark = g_quark_from_static_string ("gtk-size-request-in-progress"); - - previous_method = g_object_get_qdata (G_OBJECT (widget), recursion_check_quark); - - if (orientation == GTK_SIZE_GROUP_HORIZONTAL) - { - method = for_size < 0 ? "get_width" : "get_width_for_height"; - } - else - { - method = for_size < 0 ? "get_height" : "get_height_for_width"; - } - - if (previous_method != NULL) - { - g_warning ("%s %p: widget tried to gtk_widget_%s inside " - " GtkWidget ::%s implementation. " - "Should just invoke GTK_WIDGET_GET_CLASS(widget)->%s " - "directly rather than using gtk_widget_%s", - G_OBJECT_TYPE_NAME (widget), widget, - method, previous_method, - method, method); + cache->heights[cache->last_cached_height]->lower_for_size = for_size; + cache->heights[cache->last_cached_height]->upper_for_size = for_size; + cache->heights[cache->last_cached_height]->cached_size.minimum_size = minimum_size; + cache->heights[cache->last_cached_height]->cached_size.natural_size = natural_size; } - - g_object_set_qdata (G_OBJECT (widget), recursion_check_quark, (char*) method); -#endif /* G_DISABLE_CHECKS */ -} - -static void -pop_recursion_check (GtkWidget *widget, - GtkSizeGroupMode orientation) -{ -#ifndef G_DISABLE_CHECKS - g_object_set_qdata (G_OBJECT (widget), recursion_check_quark, NULL); -#endif } /* This is the main function that checks for a cached size and @@ -259,14 +330,14 @@ compute_size_for_orientation (GtkWidget *widget, { CachedSize *cached_size; gboolean found_in_cache = FALSE; - gint adjusted_min, adjusted_natural; + gint min_size = 0; + gint nat_size = 0; found_in_cache = get_cached_size (widget, orientation, for_size, &cached_size); if (!found_in_cache) { - gint min_size = 0; - gint nat_size = 0; + gint adjusted_min, adjusted_natural, adjusted_for_size = for_size; if (orientation == GTK_SIZE_GROUP_HORIZONTAL) { @@ -292,11 +363,11 @@ compute_size_for_orientation (GtkWidget *widget, &minimum_height, &natural_height, &ignored_position, - &for_size); + &adjusted_for_size); push_recursion_check (widget, orientation, for_size); GTK_WIDGET_GET_CLASS (widget)->get_preferred_width_for_height (widget, - MAX (for_size, minimum_height), + MAX (adjusted_for_size, minimum_height), &min_size, &nat_size); pop_recursion_check (widget, orientation); } @@ -311,9 +382,9 @@ compute_size_for_orientation (GtkWidget *widget, } else { - int ignored_position = 0; - int minimum_width; - int natural_width; + gint ignored_position = 0; + gint minimum_width; + gint natural_width; /* Pull the base natural width from the cache as it's needed to adjust * the proposed 'for_size' */ @@ -325,11 +396,11 @@ compute_size_for_orientation (GtkWidget *widget, &minimum_width, &natural_width, &ignored_position, - &for_size); + &adjusted_for_size); push_recursion_check (widget, orientation, for_size); GTK_WIDGET_GET_CLASS (widget)->get_preferred_height_for_width (widget, - MAX (for_size, minimum_width), + MAX (adjusted_for_size, minimum_width), &min_size, &nat_size); pop_recursion_check (widget, orientation); } @@ -341,16 +412,13 @@ compute_size_for_orientation (GtkWidget *widget, G_OBJECT_TYPE_NAME (widget), widget, min_size, nat_size); } - cached_size->minimum_size = min_size; - cached_size->natural_size = nat_size; - if (orientation == GTK_SIZE_GROUP_HORIZONTAL) _gtk_widget_set_width_request_needed (widget, FALSE); else _gtk_widget_set_height_request_needed (widget, FALSE); - adjusted_min = cached_size->minimum_size; - adjusted_natural = cached_size->natural_size; + adjusted_min = min_size; + adjusted_natural = nat_size; GTK_WIDGET_GET_CLASS (widget)->adjust_size_request (widget, orientation == GTK_SIZE_GROUP_HORIZONTAL ? GTK_ORIENTATION_HORIZONTAL : @@ -358,14 +426,14 @@ compute_size_for_orientation (GtkWidget *widget, &adjusted_min, &adjusted_natural); - if (adjusted_min < cached_size->minimum_size || - adjusted_natural < cached_size->natural_size) + if (adjusted_min < min_size || + adjusted_natural < nat_size) { g_warning ("%s %p adjusted size %s min %d natural %d must not decrease below min %d natural %d", G_OBJECT_TYPE_NAME (widget), widget, orientation == GTK_SIZE_GROUP_VERTICAL ? "vertical" : "horizontal", adjusted_min, adjusted_natural, - cached_size->minimum_size, cached_size->natural_size); + min_size, nat_size); /* don't use the adjustment */ } else if (adjusted_min > adjusted_natural) @@ -374,14 +442,14 @@ compute_size_for_orientation (GtkWidget *widget, G_OBJECT_TYPE_NAME (widget), widget, orientation == GTK_SIZE_GROUP_VERTICAL ? "vertical" : "horizontal", adjusted_min, adjusted_natural, - cached_size->minimum_size, cached_size->natural_size); + min_size, nat_size); /* don't use the adjustment */ } else { /* adjustment looks good */ - cached_size->minimum_size = adjusted_min; - cached_size->natural_size = adjusted_natural; + min_size = adjusted_min; + nat_size = adjusted_natural; } /* Update size-groups with our request and update our cached requests @@ -389,26 +457,31 @@ compute_size_for_orientation (GtkWidget *widget, */ _gtk_size_group_bump_requisition (widget, orientation, - &cached_size->minimum_size, - &cached_size->natural_size); + &min_size, + &nat_size); + + commit_cached_size (widget, orientation, for_size, min_size, nat_size); + } + else + { + min_size = cached_size->minimum_size; + nat_size = cached_size->natural_size; } if (minimum_size) - *minimum_size = cached_size->minimum_size; + *minimum_size = min_size; if (natural_size) - *natural_size = cached_size->natural_size; + *natural_size = nat_size; - g_assert (cached_size->minimum_size <= cached_size->natural_size); + g_assert (min_size <= nat_size); GTK_NOTE (SIZE_REQUEST, g_print ("[%p] %s\t%s: %d is minimum %d and natural: %d (hit cache: %s)\n", widget, G_OBJECT_TYPE_NAME (widget), orientation == GTK_SIZE_GROUP_HORIZONTAL ? "width for height" : "height for width" , - for_size, - cached_size->minimum_size, - cached_size->natural_size, + for_size, min_size, nat_size, found_in_cache ? "yes" : "no")); } diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index 00f9be591f..714f850b1b 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -10669,8 +10669,7 @@ gtk_widget_finalize (GObject *object) if (priv->context) g_object_unref (priv->context); - if (priv->requests.sizes) - g_slice_free (ContextualSizes, priv->requests.sizes); + _gtk_widget_free_cached_sizes (widget); if (g_object_is_floating (object)) g_warning ("A floating object was finalized. This means that someone\n" diff --git a/gtk/gtkwidgetprivate.h b/gtk/gtkwidgetprivate.h index 9dfec658e2..7d298c6471 100644 --- a/gtk/gtkwidgetprivate.h +++ b/gtk/gtkwidgetprivate.h @@ -29,12 +29,13 @@ G_BEGIN_DECLS -/* With GtkWidget, a widget may be requested - * its width for 2 or maximum 3 heights in one resize - * (Note this define is limited by the bitfield sizes - * defined on the SizeRequestCache structure). +/* Cache as many ranges of height-for-width + * (or width-for-height) as can be rational + * for a said widget to have, if a label can + * only wrap to 3 lines, only 3 caches will + * ever be allocated for it. */ -#define GTK_SIZE_REQUEST_CACHED_SIZES (2) +#define GTK_SIZE_REQUEST_CACHED_SIZES (5) typedef struct { gint minimum_size; @@ -43,26 +44,24 @@ typedef struct { typedef struct { - /* the size this request is for */ - gint for_size; + gint lower_for_size; /* The minimum for_size with the same result */ + gint upper_for_size; /* The maximum for_size with the same result */ CachedSize cached_size; } SizeRequest; typedef struct { - SizeRequest widths[GTK_SIZE_REQUEST_CACHED_SIZES]; - SizeRequest heights[GTK_SIZE_REQUEST_CACHED_SIZES]; - - guint cached_widths : 2; - guint cached_heights : 2; - guint last_cached_width : 2; - guint last_cached_height : 2; -} ContextualSizes; - -typedef struct { - ContextualSizes *sizes; - - CachedSize cached_width; - CachedSize cached_height; + SizeRequest **widths; + SizeRequest **heights; + + CachedSize cached_width; + CachedSize cached_height; + + guint cached_widths : 3; + guint cached_heights : 3; + guint last_cached_width : 3; + guint last_cached_height : 3; + guint cached_base_width : 1; + guint cached_base_height : 1; } SizeRequestCache; void _gtk_widget_set_visible_flag (GtkWidget *widget, @@ -114,6 +113,7 @@ gboolean _gtk_widget_get_translation_to_window (GtkWidget *widget, GdkWindow *window, int *x, int *y); +void _gtk_widget_free_cached_sizes (GtkWidget *widget); G_END_DECLS -- 2.30.2